还在用 websocket 做消息推送吗?试试HTTP长连接? 您所在的位置:网站首页 springboot websocket 推送消息 还在用 websocket 做消息推送吗?试试HTTP长连接?

还在用 websocket 做消息推送吗?试试HTTP长连接?

2023-05-15 03:51| 来源: 网络整理| 查看: 265

一、前言

HTTP 请求-响应模型是 Web 应用的主要通信模型,在这种模型中由客户端向服务器发起请求,服务器处理请求,要进行此通信,服务端和客户端必须先建立连接,并且当请求-响应周期结束时,连接将关闭。

这种模型对于早期的 Web 应用程序来说已经足够了,因为早期的网站只显示静态内容。但随着网络的发展,出现了允许服务器向客户端发送数据而无需客户端首先请求的需求。比如说实时聊天、多人协作、新闻消息、股票行情等。

对于这些场景传统的 HTTP 请求-响应模式并不适合,因为这种模式需要频繁建立和断开连接,造成大量的网络开销和资源浪费。因此,为了解决这种实时推送数据的问题,出现了 SSE、WebSocket 等消息推送技术。比如当前很火的 ChatGPT 的消息回复就使用了 SSE:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/42772b19952543c2a7d736abf12f68fb~tplv-k3u1fbpfcp-zoom-1.image

二、SSE 简介

Server-Sent Events (SSE) 是一种实现服务器向客户端推送事件的技术,它通过 HTTP 协议提供了一种简单的、轻量级的服务器端推送技术,可以实时地将服务器端数据推送给客户端浏览器,所以具有以下优点:

兼容性好:SSE 是使用 HTTP 协议进行通信的,因此所有基于 HTTP 协议的客户端都可以使用 SSE 接收数据。 简单易用:相比 Websocket,SSE 的 API 更加简单且易于使用,比如不需要额外的心跳检测机制。 无需建立双向连接:相比 Websocket,SSE 只需要在客户端与服务器之间建立一个单向的连接,而不是双向的连接。 三、SSE VS Websocket

websocket 是一种独立于 http 协议的持久化协议,这是相对于 http 这种 非持久 的协议来说的,它可以在用户的浏览器和服务器之间打开交互式通信会话,使用此 API,可以向服务器发送消息并接收服务器的消息。从效果和使用场景来看与 SSE 很相似,但是他们还是有比较多的不同之处:

SSEWebsocket基于 HTTP 协议独立的、基于 TCP 的全双工通信协议单工,只能由服务端向客户端发送消息全双工,双端可以同时接收和发送消息内置重连机制不支持,需要手动实现仅支持UTF-8数据传输支持二进制和UTF-8数据传输支持自定义事件类型不支持自定义事件类型连接数 HTTP/1.1 6 个,HTTP/2 可协商(默认 100)连接数无限制可以使用 JavaScript 进行 www.npmjs.com/package/eve…不能使用 JavaScript 进行 polyfill,socket.io/zh-CN/docs/… 四、SSE 基本使用 1、服务端实现 1.1、处理 HTTP 请求

SSE 本质就是浏览器发起 HTTP 请求,服务端应该能够接收到该请求,并正确地响应,具体来说,服务端需要发送以下 HTTP 头部信息:

Content-Type:设置为 text/event-stream,表示返回的内容是 SSE 格式的数据。 Cache-Control:设置为 no-cache,禁止客户端缓存响应。 Connection:设置为 keep-alive,保持长连接。 1.2、返回消息格式

EventStream(事件流)仅仅是一个简单的文本数据流,文本应该使用 UTF-8 格式的编码,每条消息由多个字段组成,这些字段包涵(event、data、id、retry),每个字段由字段名,一个冒号,以及字段值组成,比如 data: {name: ‘xx’} 。

每条消息后面都由一个空行作为分隔符(\n\n),以冒号开头的行为注释行,会被忽略,如下所示:

: this is explanatory note\n\n // 这是注释行 data: {name: 'message1'}\n\n // 这是第一条消息 event: event1\n // 这是第二条消息 data: {name: 'message2'}\n\n event: event2\n // 这是第三条消息 data: {name: 'message3-1'}\n data: {name: 'message3-2'}\\n

⚠️ 注意:

除上述四个字段外,其他所有字段都会被忽略。 如果一行字段中不包含冒号,则整行文本将被视为字段名,字段值为空。 注释行可以用来防止链接超时,服务端可以定期向浏览器发送一条消息注释行,以保持连接不断。

event

事件类型。如果指定了该字段,则在客户端接收到该条消息时,会在当前的EventSource对象上触发一个事件,事件类型就是该字段的字段值,你可以使用addEventListener() 方法在当前 EventSource对象上监听任意类型的命名事件,如果该条消息没有event字段,则会触发onmessage 属性上的事件处理函数。

服务端返回两条消息:

data: {name: 'message1'}\n\n // 这是第一条消息 event: custom-event\n // 这是第二条消息 data: {name: 'message2'}\n\n

浏览器接受这两条消息:

const evtSource = new EventSource('') // 监听自定义事件 evtSource.addEventListener('custom-event', (event) => { console.log(event.data); // {name: 'message2'} }) // 监听原生事件 evtSource.addEventListener('onmessage', (event) => { console.log(event.data); // {name: 'message1'} })

id

事件ID,用于标识事件的唯一标识符。它允许服务器为每个事件分配一个唯一的标识符,并将该标识符发送给客户端。使用事件 ID 可以轻松实现事件追踪功能,通过为每个事件分配唯一的 id,客户端可以追踪已接收的事件,如果发生断连,浏览器会把收到的最后一个事件ID放到 HTTP Header Last-Event-Id 中进行重连,以便在重新连接时,服务器可以发送丢失的事件或仅发送更新的事件。

const express = require('express'); const app = express(); const port = 8000; const eventData = []; let id = 0; app.get('/events', (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Access-Control-Allow-Origin', '*') const eventId = req.headers['Last-Event-Id']; if (eventId) { // 从数组中取出数据并发送给客户端 } const intervalId = setInterval(() => { const data = new Date().toLocaleString(); res.write(`id: ${id}\n`) res.write(`data: ${data}\n\n`) eventData.push({id, data}) id++; }, 2000) res.on('close', () => { console.log('Client closed connected') }) }) app.listen(port, () => { console.log('run server on 8000') })

对于 websocket 也有相应的事件追踪实现方案,可以参考 socket.io 的实现。

data

消息数据。数据内容只能以一个字符串的文本形式进行发送,如果需要发送一个对象时,需要将该对象以一个 JSON 格式的字符串的形式进行发送。在浏览器接收到该字符串后,再把它还原为一个 JSON 对象。

retry

重连时间。整数值,单位 ms,如果与服务器的连接丢失,浏览器将等待指定时间,然后尝试重新连接,如果该字段不是整数值,会被忽略。

2、客户端

在浏览器端,可以使用 JavaScript 的 EventSource API 创建 EventSource 对象监听服务器发送的事件。一旦建立连接,服务器就可以发送事件消息,浏览器则可以通过监听 EventSource 对象的 原生事件 onmessage、onopen 和 onerror 来处理这些消息。

2.1、建立连接

EventSource 接受两个参数:

url

url 表示事件源,一旦 EventSource 对象被创建后,浏览器立即开始对该 url 地址发送过来的事件进行监听。

options

options 是一个可选的对象,包含 withCredentials 属性,表示是否发送凭证(cookie、HTTP认证信息等)到服务端,默认为 false。

2.2、监听事件

EventSource 对象本身继承自 EventTarget 接口,因此可以使用 addEventListener() 方法来监听事件。

open 事件

连接刚打开时被调用。

eventSource.addEventListener('open', (event) => { console.log('Connection opened') })

message 事件

当接收到服务器发送的消息时触发。该事件对象的 data 属性包含了服务器发送的消息内容,这里要注意的是如果发送的是自定义事件消息,该事件将不会被触发。

eventSource.addEventListener('message', (event) => { console.log('Received message: ' + event.data); })

error 事件

当发生错误时被调用,并且在此对象上派发 [error]() 事件。

eventSource.addEventListener('error', function(event) { console.log('Error occurred: ' + event.event); })

自定义事件

EventSource 除了为我们提供原生事件还支持自定义事件,事件的名称就是服务器返回的消息中 event 字段指定的事件名称。

eventSource.addEventListener('custom-event', function(event) { console.log('custom event message: ' + event.event); }) 2.3、关闭连接

EventSource 提供了 close 方法用于关闭连接,如果连接已经被关闭,此方法不会再进行任何操作。

// 关闭连接 eventSource.close() 3、注意事项 3.1、浏览器兼容

目前大多数现代浏览器都支持 SSE,但在旧版浏览器中可能会有一些限制或不支持。

image.png 对于不支持 EventSource 的浏览器,可以使用 polyfill 实现。

3.2、连接数最大限制

在非 HTTP / 2 的场景下使用 SSE(server-sent events)会受到最大连接数的限制,这在打开各种选项卡时特别麻烦,因为该限制是针对每个浏览器的,并且被设置为一个非常低的数字(6)。该问题在 Chrome 和 Firefox 中被标记为“无法解决”。

使用 HTTP / 2 时,HTTP 同一时间内的最大连接数由服务器和客户端之间协商(默认为 100),但是升级到 HTTP / 2 需要先接入 HTTPS,如果我们做的是 ToB 的产品,这一点是需要重点考虑的,因为很可能客户就不支持 HTTPS。

3.3、消息传输质量

SSE 本身不具备保证消息传递的质量,比如断连重连后消息不会重发,这个都需要开发者额外去实现。

五、案例实践 1、服务端 const express = require('express'); const app = express(); const port = 8000; let id = 0; app.get('/events', (req, res) => { console.log(req.headers['Last-Event-Id']); res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Access-Control-Allow-Origin', '*') const intervalId = setInterval(() => { const data = new Date().toLocaleString(); res.write(`id: ${id}\n`) res.write(`data: ${data}\n\n`) id++; }, 2000) res.on('close', () => { console.log('Client closed connected') }) }) app.listen(port, () => { console.log('run server on 8000') }) 2、客户端 Document const eventSource = new EventSource(`http://localhost:8000/events`) function updateMessage (message) { const list = document.getElementById('messages'); const item = document.createElement('p'); item.textContent = message; list.appendChild(item); } eventSource.onmessage = function(event) { updateMessage(event.data); } eventSource.onerror = function() { updateMessage('Server closed connection') eventSource.close(); } 六、总结

Server-Sent Events (SSE) 是一种实现服务器向客户端推送事件的技术,它通过 HTTP 协议提供了一种简单的、轻量级的服务器端推送技术,自带断连重连,支持事件追踪,但是也存在一些缺陷,连接数限制、没有消息重发机制等。

SSE 适用于简单的单向服务推送的场景,比如新闻更新、消息通知,因为它兼容性好,对技术要求低,而对于一些需要双向通信、高实时行的场景下,Websocket 会更合适。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有